//
//	MiscPickList.m -- Pick List
//		Written by Don Yacktman Copyright (c) 1994 by Don Yacktman.
//				Version 0.1.  All rights reserved.
//
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	

#import "MiscPickList.h"
#import <sys/param.h>

#define MISC_PickListExtension "picklist"
static MiscDictionary *availableLists = nil;

@interface MiscPickList(private)

- _enableButtons:(BOOL)flag;
- _updateBrowser;

@end

@implementation MiscPickList(private)

- _enableButtons:(BOOL)flag
{
	[modifyButton setEnabled:flag];
	[addButton setEnabled:flag];
	return self;
}

- _updateBrowser
{
	// adds/removes the items in the browser as necessary
	int i, cnt;
	[self loadNibIfNeeded];
	cnt = [sortedKeys count];
	[browser renewRows:cnt cols:1];
	for (i=0; i<cnt; i++) {
	    id newCell = [browser cellAt:i :0];
		[newCell setTag:i];
		[newCell setTitle:[[sortedKeys objectAt:i] stringValue]];
	}
	[[browser sizeToCells] display];
	return self;
}

@end


@implementation MiscPickList

+ findPickListNamed:(MiscString *)aName
{
	if (!availableLists) {
		availableLists = [[MiscDictionary alloc] init];
	}
	if ([availableLists isKey:aName]) {
		return [availableLists valueForKey:aName];
	} else {
		MiscPickList *theList = [[self alloc] initForListNamed:aName];
		[availableLists insertKey:aName value:theList];
		return theList;
	}
	return nil;
}

+ (MiscString *)pickListExtension
{
	static id theExtension = nil;
	if (!theExtension)
		theExtension = [MiscString newWithString:MISC_PickListExtension];
	return theExtension;
}

+ (MiscString *)fileNameForPickListNamed:(MiscString *)aName
{
	char path[MAXPATHLEN+1];
	char *file; MiscString *fileName;

	if ([[NXBundle mainBundle]  getPath:path
			forResource:[aName stringValue]
			ofType:[[self pickListExtension] stringValue]])
		return [MiscString newWithString:path];
	// Couldn't find it in the app bundle, so we'll check the
	// various libraries and see what we can find...
	if (![MiscFileFinder findFileTypeNamed: "PickLists"]) {
		[[MiscFileFinder alloc] initName:"PickLists"
			defaultPath:
			"~/Library/PickLists:/Library/PickLists:/LocalLibrary/PickLists"
			pathVariable:"PICKLISTPATH" mode:R_OK];
	}
	fileName = [[MiscString alloc] initFromFormat:"%s.%s",
			[aName stringValue], [[self pickListExtension] stringValue]];
	file = [[MiscFileFinder findFileTypeNamed:"PickLists"]
			fullPathForFile:[fileName stringValue]];
	[fileName free];
	if (file) return [MiscString newWithString:file];
	return nil; // couldn't find it.
}

- init
{
//	return [self error:"Don't call MiscPickList's -init method!"];
	return [self initForListNamed:[MiscString newWithString:"Default"]];
}

- free
{
	[itemList freeObjects];
	[itemList free];
	[sortedKeys freeObjects];
	[sortedKeys free];
	[listPath free];
	[listName free];
	return [super free];
}

- initForListNamed:(MiscString *)aName
{
	[super init];
	autoSave = YES;
	sortedKeys = [[List alloc] init];
	itemList = nil;
	[self loadItemList:nil name:aName];
	return self;
}

- loadItemList:(MiscString *)aPath name:(MiscString *)aName
{
	if (listName) [listName free];
	listName = [aName copy];
	if (listPath) [listPath free];
	listPath = [aPath copy];
	if (!listPath) {
		listPath = [[self class] fileNameForPickListNamed:aName];
	}
	[self reloadItemList];
	return self;
}

- reloadItemList;
{
	if (itemList) {
		[itemList freeObjects];
		[itemList free];
	}
	itemList = MiscParseTableFile(listPath);
	[sortedKeys freeObjects];
	[sortedKeys empty];
	{
		void *k, *v;
		NXHashState hs = [itemList initState];
		while ([itemList nextState:&hs key:&k value:&v]) {
			[sortedKeys addObject:[(id)k copy]];
		}
		[sortedKeys miscStringSort];
	}
	changed = NO;
	return self;
}

- saveList { return [self saveListToPath:listPath]; }
- saveListToPath:(MiscString *)aPath
{
	// ***** If we can't save to a global list, then we should try to
	// save to a list in the user's ~Library directory.
	MiscWriteTableFile(aPath, itemList);
	changed = NO;
	return self;
}

- popUp:sender
{
	[self _updateBrowser];
	[browser selectCellAt:0 :0];
	[browser scrollCellToVisible:0 :0];
	[entryText takeStringValueFrom:[self selectedKey]];
	[[self window] setTitle:[listName stringValue]];
	[[self window] makeKeyAndOrderFront:self];
	// ***** probably want to position the window near the text field...
	return self;
}

- popUpForTextPal:aTextField
{
	[self setTextPal:aTextField];
	return [self popUp:nil];
}

- doEntry:sender
{
	[entryText takeStringValueFrom:[self selectedKey]];
	[textPal takeStringValueFrom:[itemList valueForKey:[self selectedKey]]];
	return self;
}

- finishEntry:sender
{
	[self doEntry:sender];
	[[self window] orderOut:self];
	return self;
}

- addItem:sender
{
	id theString = [MiscString new];
	if (![entryText respondsTo:@selector(stringValue)]) return nil;
	[theString takeStringValueFrom:entryText];
	[self addItemToList:theString value:theString];
	[self _enableButtons:NO];
	return self;
}

- modifyItem:sender
{
	// Take the selected key, delete it, and then do an add.
	[[self window] disableDisplay];
	[[self window] disableFlushWindow];
	[self removeItemFromList:[self selectedKey]];
	[self addItem:sender];
	[self _enableButtons:NO];
	[[self window] reenableDisplay];
	[[self window] reenableFlushWindow];
	[[self window] display];
	return self;
}

- addItemToList:(MiscString *)anItem
{
	[self addItemToList:anItem value:anItem];
	return self;
}

- addItemToList:(MiscString *)anItem value:(MiscString *)aValue
{
	BOOL haveIt = ([itemList isKey:anItem] ? YES : NO);

	// return if already have key _and_ it has identical value:
	if (haveIt) {
		id existingValue = [itemList valueForKey:anItem];
		if ([existingValue isEqual:aValue]) return nil; // no changes...
	}
	[[self window] disableDisplay];
	[[self window] disableFlushWindow];
	// add the item to the list, update the browser
	[itemList insertKey:anItem value:aValue];
	if (!haveIt) {
		[sortedKeys addObject:anItem];
		[sortedKeys miscStringSort];
		[self _updateBrowser]; // browser displays keys, we only update
			// if we didn't have the key before.
		[self selectKey:anItem];
	}
	changed = YES;
	if (autoSave) [self saveList];
	[[self window] reenableDisplay];
	[[self window] reenableFlushWindow];
	[[self window] display];
	return self;
}

- removeItemFromList:(MiscString *)anItem
{
	int i;

	if (![itemList isKey:anItem]) return nil;
	[itemList removeKey:anItem];
	for (i=0; i<[sortedKeys count]; i++) {
		if ([[sortedKeys objectAt:i] isEqual:anItem]) {
			[sortedKeys removeObjectAt:i];
			changed = YES;
			[self _updateBrowser];
			if (autoSave) [self saveList];
			return self;
		}
	}
	return nil;
}

- (MiscDictionary *)itemList { return itemList; }
- (BOOL)isDirty { return changed; }
- (MiscString *)listPath { return listPath; }
- (MiscString *)listName { return listName; }
- (BOOL)autoSave { return autoSave; }
- setAutoSave:(BOOL)flag { autoSave = flag; return self; }
- browser { return browser; }
- entryText { return entryText; }
- textPal { return textPal; }
- setBrowser:anObject { browser = anObject; return self; }
- setEntryText:anObject { entryText = anObject; return self; }
- setTextPal:anObject { textPal = anObject; return self; }

- nibDidLoad
{
	[browser setDoubleAction:@selector(finishEntry:)];
	return [super nibDidLoad];
}

- (BOOL)textWillChange:textObject
{
	[self _enableButtons:YES];
	return NO;
}

- (MiscString *)selectedKey
{
	static id aString = nil;
	if (!aString) aString = [MiscString new];
	[aString setStringValue:[[browser selectedCell] title]];
	return aString;
}

- selectKey:(MiscString *)aKey
{
	int i;
	for (i=0; i<[sortedKeys count]; i++) {
		if ([aKey isEqual:[sortedKeys objectAt:i]]) {
			[browser selectCellAt:i :0];
			[browser scrollCellToVisible:i :0];
			[self doEntry:browser]; // as if they clicked here...
			return self;
		}
	}
	return nil;
}

@end
